package org.upc.edu.AIAExamples.communication;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.lang.acl.ACLMessage;
import jade.tools.sniffer.ClearCanvasAction;

import java.util.ArrayList;
import java.util.Random;

public class Casa extends Agent {
	
	public static class Dades {
		
		public static boolean initialized = false;
		public static int nagents;
		public static double dades[][];		//Energies acumulades
		public static double alphes[][];	//les alphes dels agents
		public static double diners[][];	//Diners de les cases
		public static double ccentral[][];	//Comandes a la central
		private static Chart chart = null;
		public static int torn;
		
		public static void initialize(int n) {
			torn = 0;
			nagents = n;
			dades = new double[n][100];
			alphes = new double[n][100];
			diners = new double[n][100];
			ccentral = new double[n][100];
			for(int i=0; i<n; i++) {
				for(int j=0; j<100;j++) {
					dades[i][j] = Double.NaN;
					alphes[i][j] = Double.NaN;
					diners[i][j] = Double.NaN;
					ccentral[i][j] = Double.NaN;
				}
			}
		}
		
		public static void update(int nagent, double EAcumulada, double alpha, double pasta, double comanda) {
			dades[nagent][torn] = EAcumulada;
			alphes[nagent][torn] = alpha;
			diners[nagent][torn] = pasta;
			ccentral[nagent][torn] = comanda;
		}
		
		public static void draw() {
			torn++;
			if(chart!=null) chart.setVisible(false);
			chart = new Chart("Smart Cities SID",dades,alphes,diners,ccentral);
			initialized = true;
		}
	}
	
	long EAcumulada;
	ArrayList<Long> ultimsbalans;
	long mitjanabalans;
	FuncioGauss EProduida;
	FuncioGauss EConsumida;
	FuncioGauss Ingressos;
	double diners;
	int n;
	Parell[] info;
	public int Estrategia;
	private double alpha;	//estrategia num 2, modificaci� del preu segons les experi�ncies anteriors
	private Parell[][] apa;	//estrategia numero 1, "aprenentatge automatic"
	final double PREU_CENTRAL = 1;
	final long MAX_BATERIA = 5000;
	final double CONST_ALPHA = 50000d;
	
	public double round(double p) {
		return (double) ((long) (p*10000))/10000;	
	}
	
	private double milloroferta() {
		
		double millorPreu = PREU_CENTRAL;
		for (Parell p:info){
			if (p.y != 0 && p.y < millorPreu) millorPreu = p.y;
		}
		return millorPreu;
	}

	private void send(String string, int senderid, long quantitat, double preu, String s) {
		ACLMessage msg = new ACLMessage();
		AID casaid = new AID();
        casaid.setLocalName("casa"+senderid);
		msg.addReceiver(casaid);
		msg.setContent(String.valueOf(quantitat)+ ","+String.valueOf(preu));
		msg.setSender(this.getAID());
		if (string.equals("need")){
			msg.setPerformative(ACLMessage.REQUEST);	
		}
		else if (string.equals("propose")){
			msg.setPerformative(ACLMessage.PROPOSE);
		}
		else if (string.equals("accept")){
			msg.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
		}
		else if (string.equals("confirm")){
			msg.setPerformative(ACLMessage.CONFIRM);
		}
		else if (string.equals("quit")) {
			msg.setPerformative(ACLMessage.DISCONFIRM);
		}
		else{
			System.out.println("Error funcio Send: la performativa "+string+" es incorrecta");
		}
		System.out.println(this.getLocalName() + ": SEND " + string +
				 " E: " + String.valueOf(quantitat)+ ", P: "+String.valueOf(preu) +
				 " to " + casaid.getLocalName()+ " ("+s+")");
		this.send(msg);
	}

	private Parell oferta(String content) {
		String valors[] = content.split(",");
		Parell p = new Parell (Long.parseLong(valors[0]), Double.parseDouble(valors[1]));
		return p;
	}

	private int quinacasa(AID sender) {
		String s = sender.getLocalName();
		int id = Integer.parseInt(s.substring(4));
		return id;
	}
	
	public class waiterbehaviour extends CyclicBehaviour{
		
		public waiterbehaviour(Agent agent) {
			super(agent);
		}
		
		@Override
		public void action() {
			ACLMessage msg = receive();
			if(msg == null) return;
			else if(msg.getSender().getLocalName().equals("clock")) {
				casabehaviour b = new casabehaviour(myAgent);
				myAgent.addBehaviour(b);
				myAgent.removeBehaviour(this);
			}
			else if (msg.getSender().getLocalName().substring(0,4).equals("casa")) {
				if (msg.getPerformative()==ACLMessage.DISCONFIRM) return;
				else {
					int s = Integer.parseInt(msg.getSender().getLocalName().substring(4));
					send("quit",s, 0, 0, "waiter");
				}
			}
			else return;
		}
	}
	
	public class buyerbehaviour extends CyclicBehaviour{
		
		private long startime;
		boolean busy;
		boolean negociating;
		Parell[] ultimaOferta;
		ArrayList<ACLMessage> cuam;
		double millorOferta;
		
		
		public buyerbehaviour(Agent agent) {
			super(agent);
			busy = false;
			negociating = false;
			cuam = new ArrayList<ACLMessage>();
			ACLMessage buy = new ACLMessage();
			ultimaOferta = new Parell[n];
            for (int i = 0; i < n ; ++i){
            	AID casai = new AID();
	            casai.setLocalName("casa"+i);        	
	    		buy.addReceiver(casai);
	    		info[i] = new Parell();
	    		ultimaOferta[i] = new Parell();
            }
            millorOferta = PREU_CENTRAL;
            
            System.out.println(myAgent.getLocalName() + ": SEND need" +
					 " E: " + String.valueOf(-EAcumulada)+ " to BROADCAST");
            
            buy.setPerformative(ACLMessage.REQUEST);
            buy.setContent(String.valueOf(-EAcumulada)+"," + "0");
			buy.setSender(myAgent.getAID());
			myAgent.send(buy);
			startime = System.currentTimeMillis();
		}

		private int bestSeller() {
			int millor = -1;
			double cmax = 0;
			for (int i = 0; i < info.length; ++i) {
				if(info[i].x == 0 && info[i].y == 0) continue;
				if((myAgent.getLocalName().substring(4)).equals(String.valueOf(i))) continue;
				double q = Math.abs((double) info[i].x/ (double) EAcumulada);
				double p = info[i].y/PREU_CENTRAL;
				double c = q * 0.1 + (1.0 - p) * 0.9;
				if (c > cmax) {
					cmax = c;
					millor = i;
				}
			}
			return millor;
		}
		
		@Override
		public void action() {
			ACLMessage msg;
			if(cuam.isEmpty()) msg = receive();
			else {
				msg = cuam.get(0);
				cuam.remove(0);
			}
			long dtime = System.currentTimeMillis()-startime; 
			if(msg==null && dtime>2000 && !negociating) {
				System.out.println(myAgent.getLocalName()+" negociant:");
				for(int i=0; i<info.length; i++) {
					if(info[i].x>0) {
						Parell co = contraoferta(info[i], i);
						send("propose",i, co.x, co.y, "comprador");
					}
				}
				negociating = true;
	    	}
			else if(msg==null) return;
			else if(msg.getSender().getLocalName().equals("ams")) return;
			else if(msg.getSender().getLocalName().equals("clock")) {
				casabehaviour b = new casabehaviour(myAgent);
				myAgent.addBehaviour(b);
				myAgent.removeBehaviour(this);
			}
			else if(busy) {
				cuam.add(msg);
			}
			else {
				int senderid = quinacasa(msg.getSender());
				switch(msg.getPerformative()){
				case ACLMessage.PROPOSE:
					Parell novaoferta = oferta(msg.getContent());
					millorOferta = Math.min(millorOferta, novaoferta.y);
					if(dtime>2000) {
						if(novaoferta.x!=info[senderid].x) {
							millorOferta = PREU_CENTRAL;
							if (novaoferta.x > -EAcumulada) {
								info[senderid].x = -EAcumulada;
								send("propose",senderid, -EAcumulada, preuInicialCompra(), "comprador");
							}
							else {
								info[senderid] = novaoferta;
								send("propose",senderid, info[senderid].x, preuInicialCompra(), "comprador");
							}
						}
						else {
							Parell co = contraoferta(novaoferta,senderid);
							if(co.x == novaoferta.x && co.y == novaoferta.y) send("accept",senderid, co.x, co.y, "comprador"); 
							else send("propose",senderid, co.x, co.y, "comprador");
							info[senderid] = co;
						}
					}
					else info[senderid] = novaoferta;
					break;
				case ACLMessage.CONFIRM:
					millorOferta = PREU_CENTRAL;
					EAcumulada+=info[senderid].x;
					diners -= info[senderid].x*info[senderid].y;
					System.out.println(myAgent.getLocalName()+ " He comprat " + info[senderid].x + " PREU: " + info[senderid].y + " EActual: "+EAcumulada + " Diners: "+(long) diners);
					info[senderid] = new Parell();
					if(EAcumulada>=0) {
						System.out.println(myAgent.getLocalName()+" CHANGING TO WAITINGBEHAVIOUR");
						waiterbehaviour b = new waiterbehaviour(myAgent);
						myAgent.addBehaviour(b);
						myAgent.removeBehaviour(this);
					}
					else {
						busy = false;
					}
					break;
				case ACLMessage.ACCEPT_PROPOSAL:
					info[senderid] = oferta(msg.getContent());
					millorOferta = PREU_CENTRAL;
					if (-EAcumulada >= info[senderid].x) {
						send("confirm", senderid, info[senderid].x, info[senderid].y, "comprador");
						EAcumulada += info[senderid].x;
						diners -= (double)info[senderid].x*info[senderid].y;
						System.out.println(myAgent.getLocalName()+ " He comprat " + info[senderid].x + " PREU: " + info[senderid].y + " EActual: "+EAcumulada + " Diners: "+(long) diners);
						info[senderid] = new Parell();
						if(EAcumulada>=0) {
							System.out.println(myAgent.getLocalName()+" CHANGING TO WAITINGBEHAVIOUR");
							waiterbehaviour b = new waiterbehaviour(myAgent);
							myAgent.addBehaviour(b);
							myAgent.removeBehaviour(this);
						}
					}
					else {
						send("propose",senderid, EAcumulada, preuInicialCompra(), "comprador");
					}
					break;
				case ACLMessage.DISCONFIRM:
					millorOferta=PREU_CENTRAL;
					info[senderid] = new Parell();
					busy = false;
					break;
				default:
					break;
				}
			}
		}
		
		/*
		 * ofertaActual: Oferta que ha fet el venedor
		 * senderid = id del venedor
		 */
		private Parell contraoferta(Parell ofertaActual, int senderid) {
			Parell oferta = new Parell ();
			long QEnergia = ofertaActual.x;		
			if (ultimaOferta[senderid].x == 0 && ultimaOferta[senderid].y == 0){
				ultimaOferta[senderid].y = preuInicialCompra();
				ultimaOferta[senderid].x = ofertaActual.x;
			}
			long QEnergiaNova = Math.min(ultimaOferta[senderid].x, -EAcumulada);
			double preunou = ultimaOferta[senderid].y;
			//preunou += (ofertaActual.y - ultimaOferta[senderid].y)*0.15;
			preunou += (millorOferta - ultimaOferta[senderid].y)*0.15;
			System.out.println("millor oferta: " + millorOferta + " ulrima oferta: " + ultimaOferta[senderid].y);
			if (QEnergia * ofertaActual.y < diners && (ofertaActual.y - ultimaOferta[senderid].y)< 0.02 && QEnergia <= QEnergiaNova){
				millorOferta = PREU_CENTRAL;
				return ofertaActual;
			}						
			else if (QEnergia * preunou > diners){
				preunou = ultimaOferta[senderid].y;
				do {
					QEnergiaNova *=  0.9;
				}while (QEnergiaNova * preunou > diners);
			}
			
			oferta.y = round(preunou);
			oferta.x = QEnergiaNova;
			ultimaOferta[senderid] = oferta;
			return oferta;
		}

		private double preuInicialCompra() {
			return round(1-(1/(Math.log(-EAcumulada*0.002 + Math.E))));
		}
	}
	
	public class sellerbehaviour extends CyclicBehaviour{
		private long startime;
		private int context;
		private int estrategia;
		private boolean firstRound;
		
		public sellerbehaviour(Agent agent) {
			super(agent);
			for (int i = 0; i < n ; ++i){ 
    			info[i] = new Parell();
            }
			firstRound = false;
			startime = System.currentTimeMillis();
		}

		@Override
		public void action() {
			if (EAcumulada == 0) {
				System.out.println(myAgent.getLocalName()+" CHANGING TO WAITINGBEHAVIOUR");
				waiterbehaviour b = new waiterbehaviour(myAgent);
				myAgent.addBehaviour(b);
				myAgent.removeBehaviour(this);
				return;
			}
			ACLMessage msg = receive();
			long dtime = System.currentTimeMillis()-startime;
			if (msg == null && dtime > 1000 && !firstRound){
				for (int i = 0; i<n; ++i){
					if (info[i].x != 0){
						if(info[i].x>EAcumulada){
							send("propose",i, EAcumulada, preuInicial(EAcumulada), "venedor");
							info[i] = new Parell(EAcumulada, preuInicial(EAcumulada));
						}
						else {
							send("propose",i, info[i].x, preuInicial(info[i].x), "venedor");
							info[i].y =  preuInicial(info[i].x);
						}
					}
				}
				APA();
			}
			else if (msg == null) return;
			else if(msg.getSender().getLocalName().equals("ams")) return;
			else if(msg.getSender().getLocalName().equals("clock")) {
				casabehaviour b = new casabehaviour(myAgent);
				myAgent.addBehaviour(b);
				myAgent.removeBehaviour(this);
			}
			else{
				int senderid = quinacasa(msg.getSender());
				switch(msg.getPerformative()){
				case ACLMessage.REQUEST:
					info[senderid] = oferta(msg.getContent());
					if(dtime>1000) {
						if(info[senderid].x>EAcumulada){
							send("propose",senderid, EAcumulada, preuInicial(EAcumulada), "venedor");
							info[senderid] = new Parell(EAcumulada, preuInicial(EAcumulada));
						}
						else {
							send("propose",senderid, info[senderid].x, preuInicial(info[senderid].x), "venedor");
							info[senderid].y =  preuInicial(info[senderid].x);
						}
					}
					break;
				case ACLMessage.PROPOSE:
					Parell novaoferta = oferta(msg.getContent());
					//si ens demanen mes energia de la que tenim reiniciem la negociacio
					if(novaoferta.x>EAcumulada){
						info[senderid] = new Parell(EAcumulada, preuInicial(EAcumulada));
						send("propose",senderid, EAcumulada, preuInicial(EAcumulada), "venedor");
					}
					//si la quantitat que ens demanen es diferent reiniciem la negociació
					else if(novaoferta.x!=info[senderid].x) {
						info[senderid] = novaoferta;
						send("propose",senderid, info[senderid].x, preuInicial(info[senderid].x), "venedor");
					}
					else {
						double preuccoferta = contraoferta(info[senderid].x, info[senderid].y, novaoferta.y);
						if(preuccoferta == novaoferta.y) {
							send("accept", senderid, novaoferta.x, novaoferta.y, "venedor");
						}
						else {
							info[senderid].y = preuccoferta;
							send("propose",senderid, info[senderid].x, info[senderid].y, "venedor");
						}
					}
					break;
				case ACLMessage.ACCEPT_PROPOSAL:
					info[senderid] = oferta(msg.getContent());
					if (EAcumulada >= info[senderid].x) {
						send("confirm", senderid, info[senderid].x, info[senderid].y, "venedor");
						EAcumulada -= info[senderid].x;
						alpha -= (double)info[senderid].x/50000;
						diners += (double)info[senderid].x*info[senderid].y;
						apa[context][estrategia].x += (double)info[senderid].x*info[senderid].y;
						System.out.println(myAgent.getLocalName()+ " He venut " + info[senderid].x + " a preu " + info[senderid].y+ " EActual: "+EAcumulada+ " Diners: "+(long)diners);
						info[senderid] = new Parell();
					}
					else {
						send("propose",senderid, EAcumulada, preuInicial(EAcumulada), "venedor");
					}
					break;
				case ACLMessage.CONFIRM:
					info[senderid] = oferta(msg.getContent());
					EAcumulada -= info[senderid].x;
					alpha -= (double)info[senderid].x/50000;
					diners += (double)info[senderid].x*info[senderid].y;
					apa[context][estrategia].x += (double)info[senderid].x*info[senderid].y;
					System.out.println(myAgent.getLocalName()+ " He venut " + info[senderid].x + " a preu " + info[senderid].y+ " EActual: "+EAcumulada+ " Diners: "+(long)diners);
					info[senderid] = new Parell();
					break;
				default:
					break;
				}
			}
		
			
		}
		
		void APA() {
			long suma = 0;
			firstRound = true;
			for(int i = 0; i<info.length; i++) suma += info[i].x;
			
			if (EAcumulada*1.5 < suma) context = 0;
			else if (EAcumulada < suma) context = 1;
			else if (EAcumulada*0.5 > suma) context = 3;
			else context = 2;
			
			Random rand = new Random();
			int k = rand.nextInt(2);
			if (k == 0) estrategia = rand.nextInt(10);
			else estrategia = millorContext();
			
			apa[context][estrategia].x = 0;
			//System.out.println("        Context: " + context + " estrategia: " + estrategia);
		}
		
		private int millorContext() {
			int s = 0;
			long minPes = apa[context][0].x;
			for(int i=1; i<10; i++){
				if(minPes > apa[context][i].x) {
					minPes = apa[context][i].x;
					s = i;
				}
			}
			if (minPes == 0) {
				Random r = new Random();
				s = r.nextInt(10);
			}
			return s;
		}

		private double preuInicial(long QEnergia) {
			//en funció de: E Request / E Avalible
			double oferta = 0.9+(1-(double)QEnergia/(double)EAcumulada)*0.1; //sobre 1
			//en funcio de la relacio amb la energia de la bateria
			double qea = 0.8+(1-(EAcumulada/MAX_BATERIA))*0.2;
			//en funció de: E Request
			double pbase = 1.0/ Math.log((double) 0.0005*(double) QEnergia + Math.E);
			return round(pbase*PREU_CENTRAL*oferta*qea);
		}
		
		private double preuReserva(long QEnergia) {
			//en funció de: E Request / E Avalible
			double oferta = 0.9+(1-(double)QEnergia/(double)EAcumulada)*0.1; //sobre 1
			double qea = 0.8+(1-(EAcumulada/MAX_BATERIA))*0.2;
			double x;
			switch (Estrategia) {
			case 0:
				x=0.003;
				break;
			case 1:
				x = apa[context][estrategia].y;
				break;
			case 2:
				x=0.003*alpha;
			default:
				x=0.003;
				break;
			}
			//en funció de: E Request
			double pbase = 1.0/ Math.log((double) x*(double) QEnergia + Math.E);
			return round(pbase*PREU_CENTRAL*oferta*qea);
		}
		/*	QE: Quantitat d'energia del demandant
		 *  po: Preu de la oferta que li hem fet anteriorment
		 *  pd: Preu de la demanda que ens arriba
		 */
		private double contraoferta(long QE, double po, double pd) {
			//System.out.println("PREU RESERVA: "+preuReserva(QE));
			if((po-pd)<0.02) return pd;
			return round(po - 0.15*(Math.min(po - preuReserva(QE), po - pd)));
		}
	}
	
	public class casabehaviour extends OneShotBehaviour
    {
		public casabehaviour(Agent agent) {
			super(agent);
		}

		public void action() {
			alpha += (double)EAcumulada/50000;
			int nagent = quinacasa(myAgent.getAID());
			Dades.update(nagent, EAcumulada, alpha, diners, -Math.min(0d, EAcumulada));
			if (EAcumulada < 0){
				System.out.println(myAgent.getLocalName()+": PETICIO CENTRAL: " + -EAcumulada);
				ACLMessage peticio = new ACLMessage();
				peticio.setSender(myAgent.getAID());
				AID central = new AID();
		        central.setLocalName("central");
				peticio.addReceiver(central);
				peticio.setContent(String.valueOf(-EAcumulada));
				diners += round(EAcumulada*PREU_CENTRAL);
				//myAgent.send(peticio);
				EAcumulada = 0;
			}
			long balans = 0;
			balans += EProduida.next();
			balans -= EConsumida.next();
			if(ultimsbalans.size()>=10) ultimsbalans.remove(0);
			ultimsbalans.add(balans);
			EAcumulada += balans;
			EAcumulada = Math.min(EAcumulada, MAX_BATERIA);
			mitjanabalans = mitjanabalans();
			diners += (double)Ingressos.next();
			
			if(EAcumulada >= 0){
				System.out.println(myAgent.getLocalName()+": Energia: " + EAcumulada + " Diners: " + diners + " VENEDOR");
				sellerbehaviour b = new sellerbehaviour(myAgent);
				myAgent.addBehaviour(b);
				myAgent.removeBehaviour(this);
			}
			if(EAcumulada < 0){
				System.out.println(myAgent.getLocalName()+": Energia " + EAcumulada + " Diners: " + diners + " COMPRADOR");
				buyerbehaviour b = new buyerbehaviour(myAgent);
				myAgent.addBehaviour(b);
				myAgent.removeBehaviour(this);
			}
		}
    }
	
	protected void setup() {
		Object[] args = getArguments();
		System.out.println("creant "+this.getLocalName()+" amb parametres: "+args[0].toString()+" "+args[1].toString()+" "+args[2].toString()+" "+args[3].toString());
		EConsumida = new FuncioGauss(Long.parseLong(args[0].toString()),Long.parseLong(args[1].toString()));
		EProduida = new FuncioGauss(Long.parseLong(args[2].toString()),Long.parseLong(args[3].toString()));
		EAcumulada = 0;
		ultimsbalans = new ArrayList<Long>();
		mitjanabalans = 0;
		Ingressos = new FuncioGauss(Long.parseLong(args[4].toString()),Long.parseLong(args[5].toString()));
		diners = 0;
		n = Integer.parseInt(args[6].toString());
		Estrategia = Integer.parseInt(args[7].toString());
		if(!Dades.initialized) {
			Dades.initialize(n);
		}
		info = new Parell[n];
		alpha = 1d;
		apa = new Parell[4][10];
		for (int i = 0; i < 4 ; ++i){ 
			for(int j = 0; j < 10; ++j)apa[i][j] = new Parell(0, (j+1)*0.001);
        }
		casabehaviour b = new casabehaviour(this);
		this.addBehaviour(b);
	}

	public long mitjanabalans() {
		long mitja = 0;
		for(long l: ultimsbalans) {
			mitja += l;
		}
		if (mitja > 0) mitja = 0;
		return -mitja/ultimsbalans.size();
	}
}
